package practica1.controller;

import java.io.File;
import java.util.ArrayList;
import javax.swing.JOptionPane;
import practica1.Practica1;
import practica1.controller.event.ControllerExperimentoEvent;
import practica1.controller.event.ControllerListenerEvent;
import practica1.controller.event.ControllerPoblacionEvent;
import practica1.controller.listener.ControllerExperimentoListener;
import practica1.controller.listener.ControllerListener;
import practica1.controller.listener.ControllerPoblacionListener;
import practica1.language.Language;
import practica1.logic.LogicExperimento;
import practica1.logic.LogicPoblacion;
import practica1.logic.adapter.LogicExperimentoAdapter;
import practica1.logic.event.LogicExperimentoEvent;
import practica1.manejoExperimento.ExperimentoInvalidoException;
import practica1.manejoExperimento.ManejoExperimento;
import practica1.preferences.PreferencesListener;
import practica1.preferences.PreferencesXML;

/**
 * Clase que actua como punto de unión de la aplicación permitiéndo controlar
 * tanto experimentos como poblaciones y subscribirse a sus cambios.
 * @author Miguel González - Ceura
 */
public class Controller implements PreferencesListener {
    private static Controller INSTANCE = null;
    
    private PreferencesXML prefs;
    
    //Experimentos que se han abierto
    private ArrayList<LogicExperimento> experimentosAbiertos;
    
    private LogicExperimento experimentoSeleccionado;
    private LogicPoblacion poblacionSeleccionada;
    private LogicExperimento experimentoPrincipal;
    
    //Listeners
    private ArrayList<ControllerExperimentoListener> listenersExperimento;
    private ArrayList<ControllerPoblacionListener> listenersPoblacion;
    private ArrayList<ControllerListener> listenersController;

    
    private EscuchadorExperimento escuchadorExperimento;
    
    /**
     * Constructor privado de la clase
     */
    private Controller() {
        //Obtenemos las preferencias de esta clase
        prefs = PreferencesXML.getInstance();
        //Agregamos el manejador de preferencias
        prefs.addPreferencesListener(this);
        
        experimentosAbiertos = new ArrayList<LogicExperimento>();
        
        listenersExperimento = new ArrayList<ControllerExperimentoListener>();
        listenersPoblacion = new ArrayList<ControllerPoblacionListener>();
        listenersController = new ArrayList<ControllerListener>();
        
        experimentoSeleccionado = null;
        poblacionSeleccionada = null;
        experimentoPrincipal = null;
        
        escuchadorExperimento = new EscuchadorExperimento();
        
        restorePreferences();
    }
    
    /**
     * Devuelve la instancia de la clase
     * @return Controller Controlador que se enncarga de manejar los experimentos
     * y las poblaciones.
     */
    public static Controller getInstance() {
        if(INSTANCE == null) {
            INSTANCE = new Controller();
        }
        return INSTANCE;
    }
    
    /**
     * Método que se encarga de restaurar las preferencias de la aplicación,
     * en este caso de cargar los experimentos que quedaron abiertos.
     */
    private void restorePreferences() {
        int numExp = Integer.parseInt(prefs.getValue(this, "numExpAbiertos", "0"));
        
        String mensajeInvalido = "Ha habido problemas al cargar los siguientes"
                + " experimentos:\n";
        boolean mostrarMensaje = false;
        
        for(int i=0; i<numExp; i++) {
            //Recuperamos las rutas de los experimentos
            String ruta = prefs.getValue(this, "exp-" + (i + 1), "");
            
            if(!ruta.isEmpty()) {
                try {
                    addExperimento(ManejoExperimento.abrirExperimento(
                            new File(ruta)));
                } catch (ExperimentoInvalidoException ex) {
                    Practica1.log.error("Error", ex);
                    mensajeInvalido += ruta + "\n";
                    mostrarMensaje = true;
                }
            }
        }
        
        if(mostrarMensaje) {
            JOptionPane.showMessageDialog(Practica1.getInstance(),
                        mensajeInvalido, "Advertencia",
                                JOptionPane.WARNING_MESSAGE);
        }
    }
    
    /**
     * Método que es llamado al cerrar la aplicación y se encarga de guardar
     * los experimentos que han quedado abiertos
     */
    @Override
    public void savePreferences() {
        //Guardamos los experimentos abiertos
        prefs.setValue(this, "numExpAbiertos",
                Integer.toString(experimentosAbiertos.size()));
        for(int i=0; i<experimentosAbiertos.size(); i++) {
            prefs.setValue(this, "exp-" + (i + 1), 
                experimentosAbiertos.get(i).getFichExperimento()
                .getAbsolutePath());
        }
    }
    
    /**
     * Devuelve los experimentos abiertos
     * @return ArrayList<LogicExperimento> ArrayList con los experimentos abiertos
     */
    public ArrayList<LogicExperimento> getExperimentosAbiertos() {
        return experimentosAbiertos;
    }
    
    /**
     * Permite subscribirse a las escuchas del controlador
     * @param l ControllerListener
     */
    public void addControllerListener(ControllerListener l) {
        listenersController.add(l);
    }
    
    /**
     * Permite desuscribirse a las escuchas del controlador
     * @param l ControllerListener
     */
    public void removeControllerListener(ControllerListener l) {
        listenersController.remove(l);
    }
    
    /**
     * Permite suscribirse a las escuchas de los experimentos abiertos
     * @param l ControllerExperimentoListener
     */
    public void addControllerExperimentoListener(ControllerExperimentoListener l) {
        if(l != null) {
            listenersExperimento.add(l);
        }
    }
    
    /**
     * Permite desuscribirse a las esccuchas de los experimentos abiertos
     * @param l ControllerExperimentoListener
     */
    public void removeControllerExperimentoListener(
            ControllerExperimentoListener l) {
        if(l != null) {
            listenersExperimento.remove(l);
        }
    }
    
    /**
     * Permite suscribirse a las escucuchas de las poblaciones de los experimentos
     * abiertos
     * @param l ControllerPoblacionListener 
     */
    public void addControllerPoblacionListener(ControllerPoblacionListener l) {
        if(l != null) {
            listenersPoblacion.add(l);
        }
    }
    
    /**
     * Permite desuscrbirise a las escuchas de las poblaciones de los experimentos
     * abiertos
     * @param l ControllerPoblacionListener 
     */
    public void removeControllerPoblacionListener(ControllerPoblacionListener l) {
        if(l != null) {
            listenersPoblacion.remove(l);
        }
    }
    
    /**
     * Añade un experimento a la aplicación
     * @param experimento LogicExperimento
     */
    public void addExperimento(LogicExperimento experimento) {
        experimentoPrincipal = experimento;
        
        experimento.addLogicExperimentoListener(escuchadorExperimento);
        
        experimentosAbiertos.add(experimento);
        
        for(ControllerListener l : listenersController) {
            l.experimentoPrincipalChange(new ControllerListenerEvent(experimento));
        }
        
        for(ControllerExperimentoListener l : listenersExperimento) {
            l.addedExperimento(new ControllerExperimentoEvent(experimento));
        }
    }
    
    /**
     * Quita un experimento de la aplicación
     * @param experimento LogicExperimento
     */
    public void removeExperimento(LogicExperimento experimento) {
        //Cerramos primero todas las poblaciones abiertas
        experimento.cerrarTodasPoblaciones();
        
        experimentosAbiertos.remove(experimento);
        
        for(ControllerExperimentoListener l : listenersExperimento) {
            l.removedExperimento(new ControllerExperimentoEvent(experimento));
        }
    }
    
    /**
     * Añade una población a la aplicación
     * @param poblacion LogicPoblacion
     */
    public void addPoblacion(LogicPoblacion poblacion) {
        poblacion.getExperimentoPadre().addPoblacion(poblacion);
        
        //Notificamos a todos de que se ha agregado una población
        for(ControllerPoblacionListener l : listenersPoblacion) {
            l.addedPoblacion(new ControllerPoblacionEvent(poblacion));
        }
        
        //Despues abrimos la población
        abrirPoblacion(poblacion);
    }
    
    /**
     * Quita una población de la aplicación
     * @param poblacion LogicPoblacion
     */
    public void removePoblacion(LogicPoblacion poblacion) {
        poblacion.getExperimentoPadre().removePoblacion(poblacion);
        
        for(ControllerPoblacionListener l : listenersPoblacion) {
            l.removedPoblacion(new ControllerPoblacionEvent(poblacion));
        }
    }
    
    /**
     * Establece un experimento como seleccionado
     * @param experimento LogicExperimento
     */
    public void setExperimentoSeleccionado(LogicExperimento experimento) {
        experimentoSeleccionado = experimento;
    }
    
    /**
     * Devuelve el experimento seleccionado
     * @return LogicExperimento
     */
    public LogicExperimento getExperimentoSeleccionado() {
        return experimentoSeleccionado;
    }
    
    /**
     * Dice si un experimento está abierto o no
     * @param experimento LogicExperimento
     * @return boolean True si lo está, False sinó
     */
    public boolean isExperimentoAbierto(LogicExperimento experimento) {
        return experimentosAbiertos.contains(experimento);
    }
    
    /**
     * Dice si un experimento está abierto o no
     * @param fichExperimento Fichero del experimento
     * @return boolean True si lo está, False sinó
     */
    public boolean isExperimentoAbierto(File fichExperimento) {
        for(LogicExperimento experimento : experimentosAbiertos) {
            if(experimento.getFichExperimento().equals(fichExperimento)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Establece una población como seleccionada
     * @param poblacion LogicPoblacion
     */
    public void setPoblacionSeleccionada(LogicPoblacion poblacion) {
        poblacionSeleccionada = poblacion;
    }
    
    /**
     * Devuelve la población seleccionada
     * @return LogicPoblacion
     */
    public LogicPoblacion getPoblacionSeleccionada() {
        return poblacionSeleccionada;
    }
    
    /**
     * Establece un experimento como experimento principal
     * @param experimentoPrincipal LogicExperimento
     */
    public void setExperimentoPrincipal(LogicExperimento experimentoPrincipal) {
        this.experimentoPrincipal = experimentoPrincipal;
        
        for(ControllerListener l : listenersController) {
            l.experimentoPrincipalChange(
                    new ControllerListenerEvent(experimentoPrincipal));
        }
    }
    
    /**
     * Devuelve el experimento principal de la aplicación
     * @return LogicExperimento
     */
    public LogicExperimento getExperimentoPrincipal() {
        return experimentoPrincipal;
    }

    /**
     * Abre una población
     * @param poblacion LogicPoblacion
     */
    public void abrirPoblacion(LogicPoblacion poblacion) {
        //Le decimos a su experimento que abra la población
        poblacion.getExperimentoPadre().abrirPoblacion(poblacion);
    }
    
    /**
     * Cierra una población
     * @param poblacion LogicPoblacion
     */
    public void cerrarPoblacion(LogicPoblacion poblacion) {
        //Le decimos a su experimento que abra la población
        poblacion.getExperimentoPadre().cerrarPoblacion(poblacion);
    }
    
    /**
     * Muestra una población
     * @param poblacion LogicPoblacion
     */
    public void mostrarPoblacion(LogicPoblacion poblacion) {
        poblacion.getExperimentoPadre().mostrarPoblacion(poblacion);
    }

    /**
     * Método que sale de la aplicación comprobando si antes se han guardado
     * los experimentos. Además pregunta si se quiere salir.
     */
    public void salirAplicacion() {
        boolean pobNoGuardada = false;
        for(int i=0; i<experimentosAbiertos.size() && !pobNoGuardada; i++) {
            if(experimentosAbiertos.get(i).isModified()) {
                pobNoGuardada = true;
            }
            if(!pobNoGuardada) {
                ArrayList<LogicPoblacion> poblaciones = experimentosAbiertos.get(i).
                        getPoblaciones();
                for(int j=0; j< poblaciones.size() && !pobNoGuardada; j++) {
                    if(poblaciones.get(j).isModified()) {
                        pobNoGuardada = true;
                    }
                }
            }
        }
        
        if(pobNoGuardada) {
            int res = JOptionPane.showConfirmDialog(Practica1.getInstance(), 
                    Language.getI().getP("DESEA_GUARDAR_EXPERIMENTOS")
                    , Language.getI().getP("SALIR"),
                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
            if(res == JOptionPane.OK_OPTION) {
                for(LogicExperimento e : experimentosAbiertos) {
                    try {
                        ManejoExperimento.guardarExperimento(e);
                    } catch (ExperimentoInvalidoException ex) {
                        Practica1.log.error("Error", ex);
                        JOptionPane.showMessageDialog(Practica1.getInstance(),
                        Language.getI().getP("PROBLEMA_GUARDAR") + ": "
                                + e.getNombreExperimento(), 
                        Language.getI().getP("ERROR"),
                        JOptionPane.ERROR_MESSAGE);
                    }
                }
                
                PreferencesXML.getInstance().savePreferences();
                Practica1.log.info("Saliendo de la aplicación");
                System.exit(0);
            } else if(res == JOptionPane.NO_OPTION) {
                PreferencesXML.getInstance().savePreferences();
                Practica1.log.info("Fin de la aplicación");
                System.exit(0);
            }
        } else {
            int res = JOptionPane.showConfirmDialog(Practica1.getInstance(), 
                    Language.getI().getP("DESEA_SALIR_APLICACION"),
                    Language.getI().getP("SALIR"),
                    JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
            if(res == JOptionPane.OK_OPTION) {
                PreferencesXML.getInstance().savePreferences();
                Practica1.log.info("Fin de la aplicación");
                System.exit(0);
            }
        }
    }
    
    /**
     * Clase que se encarga de escuchar los eventos de los experimentos
     */
    private class EscuchadorExperimento extends LogicExperimentoAdapter {
        /**
         * Evento que se produce cuando se abre la población de un experimento
         * @param event LogicExperimentoEvent
         */
        @Override
        public void abiertaPoblacion(LogicExperimentoEvent event) {
            for(ControllerPoblacionListener l : listenersPoblacion) {
                l.abiertaPoblacion(new ControllerPoblacionEvent(
                        event.getLogicPoblacion()));
            }
        }

        /**
         * Evento que se produce cuando se cierra la población de un experimento
         * @param event LogicExperimentoEvent
         */
        @Override
        public void cerradaPoblacion(LogicExperimentoEvent event) {
            for(ControllerPoblacionListener l : listenersPoblacion) {
                l.cerradaPoblacion(new ControllerPoblacionEvent(
                        event.getLogicPoblacion()));
            }
        }

        /**
         * Evento que se produce cuando se quiere mostrar la población de un
         * experimento.
         * @param event LogicExperimentoEvent 
         */
        @Override
        public void mostrarPoblacion(LogicExperimentoEvent event) {
            //Le decimos a todos los que nos escuchan que queremos mostrar la población
            for(ControllerPoblacionListener l : listenersPoblacion) {
                l.mostrarPoblacion(new ControllerPoblacionEvent(
                        event.getLogicPoblacion()));
            }
        }

        /**
         * Evento que se produce cuando la población de un experimento cambia
         * @param event LogicExperimentoEvent
         */
        @Override
        public void poblacionChanged(LogicExperimentoEvent event) {
            for(ControllerPoblacionListener l : listenersPoblacion) {
                l.modifiedPoblacion(new ControllerPoblacionEvent(
                        event.getLogicPoblacion()));
            }
        }
    }
}
